<!--
    这是一个网页的HTML和JavaScript代码，用于创建具有脉冲效果的心形动画。
    心形是使用数学函数从一组点创建的。每个点都具有速度、大小、颜色和轨迹等特性（这会创建轨迹效果）。
    脉冲效应是通过周期性地更新点的目标位置来实现的。
    动画循环根据每个点在心形中的目标位置更新每个点的位置和速度，并在画布上绘制每个点及其轨迹。
-->
<html><head>
    <meta charset="utf-8">
    <title>送给听朵儿</title>
    <style>

        canvas {
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, .2);
        }
        #show_name {
            z-index: 1;
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%);
            background-image: -webkit-linear-gradient(left, blue, #66ffff 10%, #cc00ff 20%, #CC00CC 30%, #CCCCFF 40%, #00FFFF 50%, #CCCCFF 60%, #CC00CC 70%, #CC00FF 80%, #66FFFF 90%, blue 100%);
            /*background-image: -webkit-linear-gradient(left, #e50dff, #7bfff7 10%, #ff1d46 20%, #515fcc 30%, #CCCCFF 40%, #00FFFF 50%, #CCCCFF 60%, #CC00CC 70%, #CC00FF 80%, #66FFFF 90%, blue 100%);*/
            -webkit-text-fill-color: transparent;
            -webkit-background-clip: text;
            -webkit-background-size: 200% 100%;
            -webkit-animation: masked-animation 4s linear infinite;
            font-size: 35px;
            font-family: Microsoft YaHei;
        }

        /*
        这段代码是背景位置属性的CSS关键帧动画。
        它定义了从0%到100%的转换，其中背景位置从0 0移动到-100%0。此动画可以应用于元素，
        以创建背景图像或渐变的滑动效果。
        */
        @keyframes masked-animation {
            0% {
                background-position: 0 0;
            }
            100% {
                background-position: -100% 0;
            }
        }

    </style>
</head>
<body>

<div id="show_name">
    <!-- 送给听朵儿-->
    
</div>
<canvas id="heart" width="625" height="961"></canvas>



<script>
/*
1.关于window变量
    在上面的代码中，“window”变量指的是web浏览器环境中的全局对象。
    它表示包含JavaScript代码的浏览器窗口或选项卡。
    “窗口”对象提供了对允许与浏览器窗口交互的各种属性和方法的访问。
    它充当在网页中运行的JavaScript代码的全局作用域。

    “window”对象的一些常见用法包括：
    1.访问和操作浏览器的位置和历史记录。
    2.打开新的浏览器窗口或选项卡。
    3.管理当前窗口的大小和位置。
    4.与浏览器的文档对象交互，该对象表示网页内容。
    5.设置和检索cookie。
    6.定义和使用全局变量和函数。
    总的来说，“窗口”变量是web浏览器中JavaScript环境的重要组成部分，
    它提供了一种与浏览器窗口交互和控制浏览器窗口的方式。

2.关于 document 变量
    在上面的代码中，“document”变量指的是DocumentObjectModel（DOM）对象。
    它表示当前加载在浏览器窗口或选项卡中的网页或HTML文档。
    document对象提供对各种属性和方法的访问，这些属性和方法允许对网页内容进行操作和交互。
    它表示HTML元素的结构化表示及其在文档中的关系。
    
    document对象的一些常见用法包括：
    1.访问和操作HTML元素：document对象允许您选择和操作页面上的HTML元素，例如更改其内容、属性或样式。
    2.修改document的结构：您可以使用document对象提供的方法在文档中创建、删除或修改HTML元素。
    3.处理事件：“document”对象允许您将事件侦听器附加到HTML元素，使您能够响应用户交互，如cli

*/
    /*
    1.它首先为平滑动画设置requestAnimationFrame方法，并检查用户的设备是否是移动的。
    */
    window.requestAnimationFrame =
        window.__requestAnimationFrame ||
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        (function () {
            return function (callback, element) {
                var lastTime = element.__lastTime;
                if (lastTime === undefined) {
                    lastTime = 0;
                }
                var currTime = Date.now();
                var timeToCall = Math.max(1, 33 - (currTime - lastTime));
                window.setTimeout(callback, timeToCall);
                element.__lastTime = currTime + timeToCall;
            };
        })();
    window.isDevice = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(((navigator.userAgent || navigator.vendor || window.opera)).toLowerCase()));
    var loaded = false;


    /*
    2.然后定义init函数。此函数使用HTML画布创建一个心形动画。
    画布的大小是根据窗口的内部宽度和高度设置的，并且每当窗口大小发生变化时都会调整其大小。
    */
    var init = function () {
        if (loaded) return;
        loaded = true;
        var mobile = window.isDevice;
        //它根据窗口是否在移动设备上设置变量“koef”。如果是，则“koef”设置为0.5，否则设置为1。此变量稍后用于调整画布的大小。
        var koef = mobile ? 0.5 : 1;
        var canvas = document.getElementById('heart');
        /*
         It obtains the 2D rendering context of the canvas using the "getContext" method with the argument '2d'. 
         The resulting context is stored in the variable "ctx". 
        */
        var ctx = canvas.getContext('2d');
        var width = canvas.width = koef * innerWidth;
        var height = canvas.height = koef * innerHeight;
        var rand = Math.random;
        //它将画布上下文的填充样式设置为“rgba（0,0,0,1）”，表示完全不透明度的黑色。
        ctx.fillStyle = "rgba(0,0,0,1)";
        //它使用黑色填充样式填充画布上的矩形，覆盖画布的整个宽度和高度。
        ctx.fillRect(0, 0, width, height);

        /*
        心形是使用数学函数（heartPosition）创建的，心形的点存储在pointsOrigin数组中。
        */
        var heartPosition = function (rad) {
            //return [Math.sin(rad), Math.cos(rad)];
            //Math.pow(a,b)即 计算 a^b
            return [Math.pow(Math.sin(rad), 3), -(15 * Math.cos(rad) - 5 * Math.cos(2 * rad) - 2 * Math.cos(3 * rad) - Math.cos(4 * rad))];
        };
        
        
        var scaleAndTranslate = function (pos, sx, sy, dx, dy) {
            return [dx + pos[0] * sx, dy + pos[1] * sy];
        };


        /*
        这用于处理窗口上的“调整大小”事件。当浏览器窗口的大小发生更改时，会触发此事件。
        附加的函数根据窗口的新内部宽度和高度重新计算画布的宽度和高度，然后用新的尺寸重新绘制画布。
        */
        window.addEventListener('resize', function () {
            width = canvas.width = koef * innerWidth;
            height = canvas.height = koef * innerHeight;
            ctx.fillStyle = "rgba(0,0,0,1)";
            ctx.fillRect(0, 0, width, height);
        });

        var traceCount = mobile ? 20 : 63;
        var pointsOrigin = [];
        var i;
        var dr = mobile ? 0.3 : 0.1;
        for (i = 0; i < Math.PI * 2; i += dr) pointsOrigin.push(scaleAndTranslate(heartPosition(i), 210, 13, 0, 0));
        for (i = 0; i < Math.PI * 2; i += dr) pointsOrigin.push(scaleAndTranslate(heartPosition(i), 150, 9, 0, 0));
        for (i = 0; i < Math.PI * 2; i += dr) pointsOrigin.push(scaleAndTranslate(heartPosition(i), 90, 5, 0, 0));
        var heartPointsCount = pointsOrigin.length;

        var targetPoints = [];
        
        /*
         The  pulse  function modifies these points to create a pulsing effect.  
         pulse  function修改这些点以产生脉冲效果。
         该函数采用两个参数，“kx”和“ky”。在函数内部，有一个循环，它对“pointsOrigin”数组的元素进行迭代。
         对于每个元素，它计算x和y坐标的新值，并将它们存储在“targetPoints”数组中。

         新的x坐标是通过将原始x坐标（pointsOrigin[i][0]）乘以“kx”并加上“width”变量的一半来计算的。通过将原始y坐标（pointsOrigin[i][1]）乘以“ky”并加上“高度”变量的一半，可以类似地计算新的y坐标。
        */
        var pulse = function (kx, ky) {
            for (i = 0; i < pointsOrigin.length; i++) {
                targetPoints[i] = [];
                targetPoints[i][0] = kx * pointsOrigin[i][0] + width / 2;
                targetPoints[i][1] = ky * pointsOrigin[i][1] + height / 2;
            }
        };

        /*
        The  e  array stores information about each point, 
        including its velocity, size, color, and trace (a trail effect). 
        e阵列存储关于每个点的信息，包括其速度、大小、颜色和轨迹（轨迹效果）。
        */
        var e = [];
        for (i = 0; i < heartPointsCount; i++) {
            //它使用“Math随机”函数为变量“x”和“y”生成随机值，并将它们分别乘以画布的宽度和高度。
            var x = rand() * width;
            var y = rand() * height;
            //It creates an object and assigns it to the "e[i]" element of the array. 
            e[i] = {
                //"vx" and "vy" represent the velocity of the point and are initially set to 0. 
                vx: 0,
                vy: 0,
                //"R" represents the radius of the point and is set to 2. 
                R: 2,
                speed: rand() + 5,
                //“q”表示“heartPointsCount”范围内的随机索引，使用“Math.rrandom”函数生成并乘以“heartPointsCount”。
                q: ~~(rand() * heartPointsCount),
                D: 2 * (i % 2) - 1,
                force: 0.2 * rand() + 0.7,
                //“f”表示点的颜色，使用“hsla”格式生成，带有色调、饱和度和亮度的随机值。
                f: "hsla(0," + ~~(40 * rand() + 60) + "%," + ~~(60 * rand() + 20) + "%,.3)",
                trace: []
            };
            for (var k = 0; k < traceCount; k++) e[i].trace[k] = {x: x, y: y};
        }

        var config = {
            traceK: 0.4,
            timeDelta: 0.01
        };

        var time = 0;


        /*
        The  loop  function is the main animation loop. 
        It updates each point's position and velocity based on its target position 
        in the heart shape, and draws each point and its trace on the canvas. 
        The heart shape 'pulses' by periodically updating the target positions. 
        循环函数是主要的动画循环。
        它根据目标位置更新每个点的位置和速度
        在心形中，并在画布上绘制每个点及其轨迹。
        心形状通过周期性地更新目标位置来“脉动”。
        */
        var loop = function () {
            var n = -Math.cos(time);
            pulse((1 + n) * .5, (1 + n) * .5);
            time += ((Math.sin(time)) < 0 ? 9 : (n > 0.8) ? .2 : 1) * config.timeDelta;
            ctx.fillStyle = "rgba(0,0,0,.1)";
            ctx.fillRect(0, 0, width, height);
            for (i = e.length; i--;) {
                var u = e[i];
                var q = targetPoints[u.q];
                var dx = u.trace[0].x - q[0];
                var dy = u.trace[0].y - q[1];
                var length = Math.sqrt(dx * dx + dy * dy);
                if (10 > length) {
                    if (0.95 < rand()) {
                        u.q = ~~(rand() * heartPointsCount);
                    } else {
                        if (0.99 < rand()) {
                            u.D *= -1;
                        }
                        u.q += u.D;
                        u.q %= heartPointsCount;
                        if (0 > u.q) {
                            u.q += heartPointsCount;
                        }
                    }
                }
                u.vx += -dx / length * u.speed;
                u.vy += -dy / length * u.speed;
                u.trace[0].x += u.vx;
                u.trace[0].y += u.vy;
                u.vx *= u.force;
                u.vy *= u.force;
                for (k = 0; k < u.trace.length - 1;) {
                    var T = u.trace[k];
                    var N = u.trace[++k];
                    N.x -= config.traceK * (N.x - T.x);
                    N.y -= config.traceK * (N.y - T.y);
                }
                ctx.fillStyle = u.f;
                for (k = 0; k < u.trace.length; k++) {
                    ctx.fillRect(u.trace[k].x, u.trace[k].y, 1, 1);
                }
            }
            ctx.fillStyle = "rgba(255,255,255,1)";
            for (i = u.trace.length; i--;) ctx.fillRect(targetPoints[i][0], targetPoints[i][1], 2, 2);

            window.requestAnimationFrame(loop, canvas);
        };
        loop();
    };

    var s = document.readyState;
    /*
    document.addEventListener('DOMContentLoaded', init, false) 作用:
    这用于在DOM（文档对象模型）完全加载后执行init函数。
    这样可以确保脚本不会试图操作或访问HTML文档中可能尚未加载的元素。
    “DOMContentLoaded”事件是在初始HTML文档完全加载和解析后激发的，
    而无需等待样式表、图像和子帧完成加载。init函数包含用于创建心形并设置其动画的逻辑。
    */
    if (s === 'complete' || s === 'loaded' || s === 'interactive') init();
    else document.addEventListener('DOMContentLoaded', init, false);
</script>

</body></html>